﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using pdftron;
using pdftron.PDF;
using pdftron.SDF;

namespace SSN.Utilities.Documents {
	/// <summary>
	/// Helper object that flattens a PDF Form into a non editable PDF Document.
	/// <para>Note:  This class can only handle one page forms.</para>
	/// <para>Note:  This class will expand the pdf document to multiple pages to accommodate the data set.</para>
	/// </summary>
	public class PDFFlattener {

		#region Properties

		private List<Page> generatedPages=new List<Page>();

		/// <summary>When flattening a document, should generation stop if a field is not found in the document that is in the property map or vice versa.</summary>
		public bool ReturnOnFieldNotFound { get; set; }

		/// <summary>The XML document to flatten fields from.</summary>
		public XmlDocument PropertyMap { get; set; }

		private PDFDoc generatedDocument;
		/// <summary>The flattened document.</summary>
		public PDFDoc GeneratedDocument {
			get { return generatedDocument; }
			protected set { generatedDocument=value; }
		}

		private string templatePath;
		/// <summary>The path of the template PDF.</summary>
		public string TemplatePath {
			get { return templatePath; }
			set {
				if(!File.Exists(value))
					throw new ApplicationException("The template pdf could not be found.");
				templatePath = value;
			}
		}

		#endregion

		#region Constructors

		/// <summary>
		/// Creates a new PDFFlattener instance with the given template document and properties map.
		/// </summary>
		/// <param name="templatePath">The path to the template pdf.</param>
		/// <param name="propertyMap">The properties map encoded in XML format.</param>
		public PDFFlattener(string templatePath,XmlDocument propertyMap) {
			PDFNet.Initialize("Securities Service Network(ssnetwork.com):CPU:4::W:AMC(20120428):6B5E0D1F2565AFCAF1B425461F9CC2EAB0AC5494369E43EF4E01C87EF0FA");
			TemplatePath=templatePath;
			PropertyMap=propertyMap;
		}

		/// <summary>
		/// Destructor.
		/// </summary>
		~PDFFlattener() {
			if(GeneratedDocument!=null) {
				GeneratedDocument.Close();
				GeneratedDocument.Dispose();
			}
			PDFNet.Terminate();
		}

		#endregion

		#region Helper Methods

		/// <summary>
		/// Saves the flattened document to the specified path.
		/// </summary>
		/// <param name="path">The path to save the document to.</param>
		/// <returns>True or false if saving is successful.</returns>
		public bool SaveDocument(string path) {
			if(!string.IsNullOrWhiteSpace(path)&&GeneratedDocument!=null) {
				GeneratedDocument.Save(path,SDFDoc.SaveOptions.e_incremental);
				GeneratedDocument.Close();
				return true;
			}
			return false;
		}

		/// <summary>
		/// Flattens the pdf template based on the property map.
		/// </summary>
		/// <returns>True or false if flattening was successful.</returns>
		public bool FlattenDocument() {
			XmlNodeList nodeList=PropertyMap.SelectNodes("//record");
			byte[] templateData=File.ReadAllBytes(TemplatePath);
			PDFDoc currentPage=new PDFDoc(templateData,templateData.Length);
			currentPage.InitSecurityHandler();
			int currentRow=0,currentPageNumber=1;
			List<Field> currentPageFields=GetTemplateFields(currentPage);
			Field pageNumField=currentPageFields.Find(field => field.GetName().ToLower()=="pagenumber");
			if(pageNumField!=null)
				pageNumField.SetValue(currentPageNumber.ToString());
			foreach(XmlNode node in nodeList) {
				List<Field> rowFields=currentPageFields.FindAll(field => field.GetName().EndsWith("." + currentRow.ToString()));
				if(rowFields.Count==0) {
					currentPage.RefreshFieldAppearances();
					generatedPages.Add(currentPage.GetPage(1));
					currentPage=new PDFDoc(templateData,templateData.Length);
					currentPage.InitSecurityHandler();
					currentRow=0;
					currentPageFields=GetTemplateFields(currentPage);
					pageNumField=currentPageFields.Find(field => field.GetName().ToLower()=="pagenumber");
					if(pageNumField!=null)
						pageNumField.SetValue((++currentPageNumber).ToString());
					rowFields=currentPageFields.FindAll(field => field.GetName().EndsWith("." + currentRow.ToString()));
				}
				foreach(XmlNode fieldNode in node.ChildNodes) {
					Field currentField = rowFields.Find(f => f.GetName().ToLower().StartsWith(fieldNode.Name.ToLower()));
					if(currentField==null) {
						if(ReturnOnFieldNotFound)
							return false;
						else
							continue;
					}
					currentField.SetValue(fieldNode.InnerText);
				}
				currentRow++;
			}
			currentPage.RefreshFieldAppearances();
			generatedPages.Add(currentPage.GetPage(1));
			GeneratedDocument=new PDFDoc();
			foreach(Page page in generatedPages)
				GeneratedDocument.PagePushBack(page);
			return true;
		}

		/// <summary>
		/// Returns a list of fields in the template document.
		/// </summary>
		/// <param name="document">The document to search through.</param>
		/// <returns>The list of fields.</returns>
		private List<Field> GetTemplateFields(PDFDoc document) {
			List<Field> fields=new List<Field>();
			for(FieldIterator iterator=document.GetFieldIterator();iterator.HasNext();iterator.Next())
				fields.Add(iterator.Current());
			return fields;
		}

		#endregion
	}
}
